/* **********************************************************
 * Copyright 2010 Dimitar Vangelovski
 *
 * This file is part of the Gens3 3D Engine.
 * 
 * The Gens3 3D Engine is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 * 
 * The Gens3 3D Engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with the Gens3 3D Engine; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 * **********************************************************/


#include "common.h"
#include "Lighting.h"
#include "Material.h"
#include "SubMaterial.h"
#include "LightObject.h"
#include "Scene.h"

int GL_Light[8] = {GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, GL_LIGHT3, GL_LIGHT4, GL_LIGHT5, GL_LIGHT6, GL_LIGHT7};

// setup material lighting properties
void SetupMaterial(int mat_ref, CScene * pScene)
{
	GLfloat ambient_reflectance[] = { pScene->GetMaterial(mat_ref)->GetMaterialAmbient().GetR(),
					  pScene->GetMaterial(mat_ref)->GetMaterialAmbient().GetG(),
					  pScene->GetMaterial(mat_ref)->GetMaterialAmbient().GetB(), 1.0f };

	GLfloat diffuse_reflectance[] = { pScene->GetMaterial(mat_ref)->GetMaterialDiffuse().GetR(), 
					  pScene->GetMaterial(mat_ref)->GetMaterialDiffuse().GetG(), 
					  pScene->GetMaterial(mat_ref)->GetMaterialDiffuse().GetB(), 1.0f };

	GLfloat specular_level = pScene->GetMaterial(mat_ref)->GetMaterialShineStrength() * 100.0f;

	GLfloat specular_reflectance[] = { pScene->GetMaterial(mat_ref)->GetMaterialSpecular().GetR() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetMaterialSpecular().GetG() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetMaterialSpecular().GetB() * specular_level, 1.0f };

	GLfloat specular_exponent = pScene->GetMaterial(mat_ref)->GetMaterialShine() * 128.0f;

	GLfloat self_illumination = pScene->GetMaterial(mat_ref)->GetMaterialSelfIllum();
	GLfloat material_emission[] = { self_illumination, self_illumination, self_illumination, 1.0f };

	glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_reflectance);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_reflectance);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specular_reflectance);
	glMaterialf(GL_FRONT, GL_SHININESS, specular_exponent);
	glMaterialfv(GL_FRONT, GL_EMISSION, material_emission);
}

// setup submaterial lighting properties
void SetupSubMaterial(int mat_ref, int mat_id, CScene * pScene)
{
	GLfloat ambient_reflectance[] = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialAmbient().GetR(),
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialAmbient().GetG(),
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialAmbient().GetB(), 1.0f };

	GLfloat diffuse_reflectance[] = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialDiffuse().GetR(), 
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialDiffuse().GetG(), 
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialDiffuse().GetB(), 1.0f };

	GLfloat specular_level = pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialShineStrength() * 100.0f;

	GLfloat specular_reflectance[] = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSpecular().GetR() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSpecular().GetG() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSpecular().GetB() * specular_level, 1.0f };

	GLfloat specular_exponent = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialShine() * 128.0f };

	GLfloat self_illumination = pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSelfIllum();
	GLfloat material_emission[] = { self_illumination, self_illumination, self_illumination, 1.0f };

	glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_reflectance);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_reflectance);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specular_reflectance);
	glMaterialf(GL_FRONT, GL_SHININESS, specular_exponent);
	glMaterialfv(GL_FRONT, GL_EMISSION, material_emission);
}

// setup material lighting properties for transparent materials
void SetupMaterialAlpha(int mat_ref, CScene * pScene)
{
	GLfloat ambient_reflectance[] = { pScene->GetMaterial(mat_ref)->GetMaterialAmbient().GetR(),
					  pScene->GetMaterial(mat_ref)->GetMaterialAmbient().GetG(),
					  pScene->GetMaterial(mat_ref)->GetMaterialAmbient().GetB(), 1.0f };

	GLfloat diffuse_reflectance[] = { pScene->GetMaterial(mat_ref)->GetMaterialDiffuse().GetR(), 
					  pScene->GetMaterial(mat_ref)->GetMaterialDiffuse().GetG(), 
					  pScene->GetMaterial(mat_ref)->GetMaterialDiffuse().GetB(), 1 - pScene->GetMaterial(mat_ref)->GetMaterialTransparency() };

	GLfloat specular_level = pScene->GetMaterial(mat_ref)->GetMaterialShineStrength();

	GLfloat specular_reflectance[] = { pScene->GetMaterial(mat_ref)->GetMaterialSpecular().GetR() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetMaterialSpecular().GetG() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetMaterialSpecular().GetB() * specular_level, 1.0f };

	GLfloat specular_exponent = pScene->GetMaterial(mat_ref)->GetMaterialShine() * 128.0f;

	GLfloat self_illumination = pScene->GetMaterial(mat_ref)->GetMaterialSelfIllum();
	GLfloat material_emission[] = { self_illumination, self_illumination, self_illumination, 1.0f };

	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient_reflectance);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse_reflectance);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular_reflectance);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, specular_exponent);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_emission);
}

// setup submaterial lighting properties for transparent materials
void SetupSubMaterialAlpha(int mat_ref, int mat_id, CScene * pScene)
{
	GLfloat ambient_reflectance[] = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialAmbient().GetR(),
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialAmbient().GetG(),
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialAmbient().GetB(), 1.0f };

	GLfloat diffuse_reflectance[] = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialDiffuse().GetR(), 
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialDiffuse().GetG(), 
					  pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialDiffuse().GetB(), 1 - pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialTransparency() };

	GLfloat specular_level = pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialShineStrength();

	GLfloat specular_reflectance[] = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSpecular().GetR() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSpecular().GetG() * specular_level,
					   pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSpecular().GetB() * specular_level, 1.0f };

	GLfloat specular_exponent = { pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialShine() * 128.0f };

	GLfloat self_illumination = pScene->GetMaterial(mat_ref)->GetSubMaterial(mat_id)->GetMaterialSelfIllum();
	GLfloat material_emission[] = { self_illumination, self_illumination, self_illumination, 1.0f };

	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient_reflectance);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse_reflectance);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular_reflectance);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, specular_exponent);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_emission);
}

// setup material lighting properties for the default material
void SetupMaterial_NoName()
{
	GLfloat ambient_reflectance[] = { 0.33f, 0.33f, 0.33f, 1.0f };
	GLfloat diffuse_reflectance[] = { 0.33f, 0.33f, 0.33f, 1.0f };
	GLfloat specular_reflectance[] = { 0.33f, 0.33f, 0.33f, 1.0f };
	GLfloat specular_exponent = 33.333f;
	GLfloat material_emission[] = { 0.33f, 0.33f, 0.33f, 1.0f };

	glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_reflectance);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_reflectance);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specular_reflectance);
	glMaterialf(GL_FRONT, GL_SHININESS, specular_exponent);
	glMaterialfv(GL_FRONT, GL_EMISSION, material_emission);
}

// setup lights properties
void SetupLights(CScene * pScene)
{
	int clight = -1;
	GLfloat Color_R, Color_G, Color_B;
	GLfloat global_ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f };

	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);	// Global Ambient Light Illumination
	//glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_true);	// Make specular lighting relative to the viewer's eye
	//glEnable(GL_LIGHTING);

	// Go through all the lights in the world
	for (int i=0; i<pScene->GetLightObjectCount(); i++)
	{
		if (pScene->GetLightObject(i)->GetLightUseLight())	// check if the light is disabled
		{
			Color_R = pScene->GetLightObject(i)->GetLightColor().r;
			Color_G = pScene->GetLightObject(i)->GetLightColor().g;
			Color_B = pScene->GetLightObject(i)->GetLightColor().b;
			GLfloat LightAmbient[]={ Color_R * 0.1f, Color_G * 0.1f, Color_B * 0.1f, 1.0f };
			GLfloat LightDiffuse[]={ Color_R, Color_G, Color_B, 1.0f };
			GLfloat LightSpecular[]={ Color_R, Color_G, Color_B, 1.0f };

			clight++;
			glLightfv(GL_Light[clight], GL_AMBIENT, LightAmbient);		// Setup Ambient Component
			glLightfv(GL_Light[clight], GL_DIFFUSE, LightDiffuse);		// Setup Diffuse Component
			glLightfv(GL_Light[clight], GL_SPECULAR, LightSpecular);	// Setup Specular Component
			glLightf(GL_Light[clight], GL_CONSTANT_ATTENUATION, 1.0f);	// Enable the constant attenuation
			glLightf(GL_Light[clight], GL_QUADRATIC_ATTENUATION, 1.0f / pScene->GetLightObject(i)->GetLightIntens()); // Enable quadratic attenuation
			glEnable(GL_Light[clight]);					// Enable Lights
		}
	}
}

// update light world position
void UpdateLights(CScene * pScene)
{
	int clight = -1;

	// Go through all the lights in the world
	for (int i=0; i<pScene->GetLightObjectCount(); i++)
	{
		if (pScene->GetLightObject(i)->GetLightUseLight())	// check if the light is disabled
		{
			GLfloat LightPosition[] = { pScene->GetLightObject(i)->GetTmPos().x, pScene->GetLightObject(i)->GetTmPos().y, pScene->GetLightObject(i)->GetTmPos().z, 1.0f };

			clight++;
			glLightfv(GL_Light[clight], GL_POSITION, LightPosition);	// Position The Light
		}
	}
}

// calculate a normal for the triangle defined with vertices v0, v1 and v2
CMeshVertexNormal CalculateNormal(CVertex v0, CVertex v1, CVertex v2)
{
	CVertex vVector1 = CVertex(v1.x - v0.x, v1.y - v0.y, v1.z - v0.z);
	CVertex vVector2 = CVertex(v2.x - v0.x, v2.y - v0.y, v2.z - v0.z);

	CMeshVertexNormal vNormal = CMeshVertexNormal(0, vVector1.y * vVector2.z - vVector1.z * vVector2.y, 
							 vVector1.z * vVector2.x - vVector1.x * vVector2.z,
							 vVector1.x * vVector2.y - vVector1.y * vVector2.x);

	float modul = static_cast<float>(sqrt(vNormal.x * vNormal.x + vNormal.y * vNormal.y + vNormal.z * vNormal.z));

	vNormal.x /= modul;
	vNormal.y /= modul;
	vNormal.z /= modul;

	return vNormal;
}
